package com.yonyou.pmclouds.basecom.lock;

import com.yonyou.pmclouds.basecom.annotation.Lock;
import com.yonyou.pmclouds.basecom.entity.SuperVO;
import com.yonyou.pmclouds.basecom.exception.BusinessRuntimeException;
import com.yonyou.pmclouds.basecom.multilanguage.ResUtil;
import com.yonyou.pmclouds.lock.service.itf.LockService;
import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 自动动态锁，加锁只需要在方法上增加注解lock，value为参数名，
 * 类型只支持String和SuperVO，
 * 如果是string则直接加锁，如果是SuperVO则获取该VO的主键值
 * 如果没有指定，则获取第一个参数值
 * 如果该方法没有参数，则报错
 * 此外加锁后可能出现的脏读问题需要处理
 * 增加固定值加锁statickey
 * @author zm
 */
@Aspect
@Component
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DynamicLock {
	
	@Autowired
	@Setter
	@Getter
	private LockService lockService;
	@Around("@annotation(com.yonyou.pmclouds.basecom.annotation.Lock)")
	public Object invoke(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

		Object obj;
		String lock = null;
		String methodName=null;
        SuperVO superVo = null;

		Method sign = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
		Lock lo = sign.getAnnotation(Lock.class);
		String key = lo.value();
		long waittime = lo.waittime();
		long timeout = lo.timeout();
		boolean checkstatus = lo.checkstatus();
		String statickey = lo.staticvalue();

		if(statickey!=null&&!statickey.equals(""))
		{
			lock = statickey;
		}
		else
		{
			Object[] args = proceedingJoinPoint.getArgs();
			if (args == null || args.length==0) {
				throw new BusinessRuntimeException(ResUtil.getInstance().getResString("该方法没有任何参数"));
			}
			methodName = sign.getName();
			Map<String,Object > map = getFieldsName(this.getClass(),proceedingJoinPoint.getTarget().getClass().getName(),methodName,args);

			if(key==null||key.equals(""))
			{

				if(args[0] instanceof SuperVO)
				{
					lock = String.valueOf(((SuperVO)args[0]).pkFiledValue());
					superVo = (SuperVO)args[0];
				}
				else if(args[0] instanceof String)
				{
					lock = (String)args[0];
					checkstatus = false;
				}
				else
				{
					throw new BusinessRuntimeException(ResUtil.getInstance().getResString("不支持的加锁类型"));
				}
			}
			else if(map.containsKey(key))
			{
				if(map.get(key) instanceof SuperVO)
				{
					lock = String.valueOf(((SuperVO)map.get(key)).pkFiledValue());
					superVo = (SuperVO)map.get(key);
				}
				else if(map.get(key) instanceof String)
				{
					lock = (String)map.get(key);
					checkstatus = false;
				}
				else
				{
					throw new BusinessRuntimeException(ResUtil.getInstance().getResString("不支持的加锁类型"));
				}
			}
			else
			{
				throw new BusinessRuntimeException(ResUtil.getInstance().getResString("找不到加锁参数"));
			}
		}
		try {
			// 重入锁
			Boolean bo = PMLock.addLock(lock,waittime,timeout);
			if(!bo.booleanValue())
			{
				throw new BusinessRuntimeException(ResUtil.getInstance().getResString("加锁失败"));
			}
			//加锁完成后验证数据有效性
			if(checkstatus&&superVo!=null)
			{
				lockService.checkFlag(superVo);
			}
			
			obj = proceedingJoinPoint.proceed();
		} finally {
			// 重入锁删除
			PMLock.releaseLock(lock);
		}
		return obj;
	}

	private Map<String,Object> getFieldsName(Class cls, String clazzName, String methodName, Object[] args) throws Exception {   
        Map<String,Object > map=new LinkedHashMap<String,Object>(); 


        ClassPool pool = ClassPool.getDefault();    
        ClassClassPath classPath = new ClassClassPath(cls);    
        pool.insertClassPath(classPath);    
            
        CtClass cc = pool.get(clazzName);    
        CtMethod cm = cc.getDeclaredMethod(methodName);    
        MethodInfo methodInfo = cm.getMethodInfo();  
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();    
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);    
        if (attr == null) {    
        	throw new BusinessRuntimeException(ResUtil.getInstance().getResString("该方法没有任何参数"));
        }    
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;    
        for (int i = 0; i < cm.getParameterTypes().length; i++){    
            map.put( attr.variableName(i + pos),args[i]);//paramNames即参数名    
        }    
        return map;    
    }   
}
